﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace HeritageAcademy.Library.Controls
{
    public class AdorningListBox : ListBox
    {
        #region Parameters 
        Storyboard _sb;
        #endregion

        #region Ctor 
        public AdorningListBox()
        {
            Loaded += AdorningListBox_Loaded;
            SizeChanged += AdorningListBox_SizeChanged;
        }
        #endregion

        #region Event Handlers 
        void AdorningListBox_Loaded(object sender, RoutedEventArgs e)
        {
            UpdateSelectionAdorner();
        }

        void AdorningListBox_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateSelectionAdorner();
        }
        #endregion Event Handlers 

        #region Protected Methods 

        protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
        {
            base.OnItemsSourceChanged(oldValue, newValue);
            UpdateSelectionAdorner();
        }

        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            base.OnSelectionChanged(e);
            UpdateSelectionAdorner();
        }

        #endregion Protected Methods 

        #region Private Methods 

        private void UpdateSelectionAdorner()
        {

            // get the width, height and offset of the ui associated with the selected item
            if (SelectedIndex >= 0)
            {
                SetAdorningElementOpacity(1.0);

                var element = ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as FrameworkElement;

                if (element != null)
                {
                    var transform = element.TransformToAncestor(this);
                    var offset = transform.Transform(new Point(0, 0));

                    if (Duration.Milliseconds == 0 || !IsAnimationEnabled)
                    {
                        this.SelectedElementOffsetX = offset.X;
                        this.SelectedElementOffsetY = offset.Y;
                        this.SelectedElementHeight = element.ActualHeight;
                        this.SelectedElementWidth = element.ActualWidth;
                    }
                    else
                    {
                        if (_sb == null)
                        {
                            _sb = new Storyboard();

                            // offsetX
                            DoubleAnimationUsingKeyFrames offsetx = new DoubleAnimationUsingKeyFrames();
                            Storyboard.SetTargetProperty(offsetx, new PropertyPath("SelectedElementOffsetX"));
                            Storyboard.SetTarget(offsetx, this);
                            offsetx.KeyFrames.Add(new SplineDoubleKeyFrame());
                            _sb.Children.Add(offsetx);

                            // width
                            DoubleAnimationUsingKeyFrames width = new DoubleAnimationUsingKeyFrames();
                            Storyboard.SetTargetProperty(width, new PropertyPath("SelectedElementWidth"));
                            Storyboard.SetTarget(width, this);
                            width.KeyFrames.Add(new SplineDoubleKeyFrame());
                            _sb.Children.Add(width);

                        }

                        // update values
                        (_sb.Children[0] as DoubleAnimationUsingKeyFrames).KeyFrames[0].Value = offset.X;
                        (_sb.Children[1] as DoubleAnimationUsingKeyFrames).KeyFrames[0].Value = element.ActualWidth;

                        // udpate time
                        TimeSpan duration = this.Duration;
                        (_sb.Children[0] as DoubleAnimationUsingKeyFrames).KeyFrames[0].KeyTime = duration;
                        (_sb.Children[1] as DoubleAnimationUsingKeyFrames).KeyFrames[0].KeyTime = duration;

                        // update easing
                        if (this.Easing != null)
                        {
                            ((_sb.Children[0] as DoubleAnimationUsingKeyFrames).KeyFrames[0] as SplineDoubleKeyFrame).KeySpline = this.Easing;
                            ((_sb.Children[1] as DoubleAnimationUsingKeyFrames).KeyFrames[0] as SplineDoubleKeyFrame).KeySpline = this.Easing;
                        }

                        this.BeginStoryboard(_sb);
                    }
                }
            }
            else
            {
                this.SetAdorningElementOpacity(0.0);
            }
        }

        #endregion Private Methods 

        #region Duration

        /// <summary>
        /// Duration Dependency Property
        /// </summary>
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((TimeSpan)TimeSpan.FromMilliseconds(500)));

        /// <summary>
        /// Gets or sets the Duration property.  This dependency property 
        /// indicates ....
        /// </summary>
        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        #endregion

        #region Easing

        /// <summary>
        /// Easing Dependency Property
        /// </summary>
        public static readonly DependencyProperty EasingProperty =
            DependencyProperty.Register("Easing", typeof(KeySpline), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((KeySpline)new KeySpline(0, 0.5, 0.5, 1)));

        /// <summary>
        /// Gets or sets the Easing property.  This dependency property 
        /// indicates ....
        /// </summary>
        public KeySpline Easing
        {
            get { return (KeySpline)GetValue(EasingProperty); }
            set { SetValue(EasingProperty, value); }
        }

        #endregion

        #region AdorningElementOpacity

        /// <summary>
        /// AdorningElementOpacity Read-Only Dependency Property
        /// </summary>
        private static readonly DependencyPropertyKey AdorningElementOpacityPropertyKey
            = DependencyProperty.RegisterReadOnly("AdorningElementOpacity", typeof(double), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((double)0.0));

        public static readonly DependencyProperty AdorningElementOpacityProperty
            = AdorningElementOpacityPropertyKey.DependencyProperty;

        /// <summary>
        /// Gets the AdorningElementOpacity property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double AdorningElementOpacity
        {
            get { return (double)GetValue(AdorningElementOpacityProperty); }
        }

        /// <summary>
        /// Provides a secure method for setting the AdorningElementOpacity property.  
        /// This dependency property indicates ....
        /// </summary>
        /// <param name="value">The new value for the property.</param>
        protected void SetAdorningElementOpacity(double value)
        {
            SetValue(AdorningElementOpacityPropertyKey, value);
        }

        #endregion

        #region SelectedElementOffsetX

        /// <summary>
        /// SelectedElementOffsetX Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedElementOffsetXProperty =
            DependencyProperty.Register("SelectedElementOffsetX", typeof(double), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((double)0.0));

        /// <summary>
        /// Gets or sets the SelectedElementOffsetX property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double SelectedElementOffsetX
        {
            get { return (double)GetValue(SelectedElementOffsetXProperty); }
            set { SetValue(SelectedElementOffsetXProperty, value); }
        }

        #endregion

        #region SelectedElementOffsetY

        /// <summary>
        /// SelectedElementOffsetY Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedElementOffsetYProperty =
            DependencyProperty.Register("SelectedElementOffsetY", typeof(double), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((double)0.0));

        /// <summary>
        /// Gets or sets the SelectedElementOffsetY property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double SelectedElementOffsetY
        {
            get { return (double)GetValue(SelectedElementOffsetYProperty); }
            set { SetValue(SelectedElementOffsetYProperty, value); }
        }

        #endregion

        #region SelectedElementWidth

        /// <summary>
        /// SelectedElementWidth Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedElementWidthProperty =
            DependencyProperty.Register("SelectedElementWidth", typeof(double), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((double)0.0));

        /// <summary>
        /// Gets or sets the SelectedElementWidth property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double SelectedElementWidth
        {
            get { return (double)GetValue(SelectedElementWidthProperty); }
            set { SetValue(SelectedElementWidthProperty, value); }
        }

        #endregion

        #region SelectedElementHeight

        /// <summary>
        /// SelectedElementHeight Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedElementHeightProperty =
            DependencyProperty.Register("SelectedElementHeight", typeof(double), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((double)0.0));

        /// <summary>
        /// Gets or sets the SelectedElementHeight property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double SelectedElementHeight
        {
            get { return (double)GetValue(SelectedElementHeightProperty); }
            set { SetValue(SelectedElementHeightProperty, value); }
        }

        #endregion

        #region IsAnimationEnabled

        /// <summary>
        /// IsAnimationEnabled Dependency Property
        /// </summary>
        public static readonly DependencyProperty IsAnimationEnabledProperty =
            DependencyProperty.Register("IsAnimationEnabled", typeof(bool), typeof(AdorningListBox),
                new FrameworkPropertyMetadata((bool)true));

        /// <summary>
        /// Gets or sets the IsAnimationEnabled property.  This dependency property 
        /// indicates ....
        /// </summary>
        public bool IsAnimationEnabled
        {
            get { return (bool)GetValue(IsAnimationEnabledProperty); }
            set { SetValue(IsAnimationEnabledProperty, value); }
        }

        #endregion
    }
}
